﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Xml;
using System.Windows.Forms;
using MacomberMapSystem.Common.Database;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing.Design;
using MacomberMapSystem.Common.User_Interfaces.Processing;
using System.Threading;
using MacomberMapSystem.Common.User_Interfaces;
using MacomberMapSystem.Common.User_Interfaces.Editors;
using MacomberMapSystem.Common.Internals;
using System.Reflection;
using System.IO;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// This class holds information on a CIM processor
    /// </summary>
    public abstract class CIM_Processor
    {
      
        #region Variable declarations
        /// <summary>Our thread that handles the process queue</summary>
        public static Thread ProcessQueue;
  
        /// <summary>
        /// Return our master delta log
        /// </summary>
        public List<CIM_Processor_Delta> Deltas
        {
            get
            {
                if (NamedParameters == null)
                {
                    NamedParameters = new Dictionary<string, object>();
                    NamedParameters.Add("Deltas", new List<CIM_Processor_Delta>());
                }
                else if (!NamedParameters.ContainsKey("Deltas"))
                    NamedParameters.Add("Deltas", new List<CIM_Processor_Delta>());
                return NamedParameters["Deltas"] as List<CIM_Processor_Delta>;
            }
        }

        /// <summary>Our current value</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]
        internal float Current=0f;

        /// <summary>Our total number of steps</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]
        internal float Total=0f;
      
        /// <summary>
        /// Report the progress on our run
        /// </summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None),Browsable(false)]
        public float Progress
        {
            get
            {
                if (Total == 0)
                    return 0;
                else
                    return Current / Total;
            }
        }

        /// <summary>The checkbox associated with this item</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]        
        public MM_Progress_Checkbox ProgressCheck;

        /// <summary>The description of our item</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public String Description;

        /// <summary>Report the process name</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None),Browsable(false)]
        
        public abstract string ProcessName { get; }

        /// <summary>Whether the process is selected</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Browsable(false)]
        public bool Selected
        {
            get { return ProgressCheck.Checked; }
            set { ProgressCheck.Checked = value; }
        }

        /// <summary>Whether this item has properties</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public bool HasProperties = true;

        /// <summary>The step this process is run on</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public int Step = -1;

        /// <summary>Whether this process should be fired off after another process completes</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public bool OnComplete = false;

        /// <summary>The log of events</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]
        public StringBuilder ProcessLog = new StringBuilder();

        /// <summary>The repository to be used for htis process</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]
        public MM_Repository Repository;


        /// <summary>The named parameters for the application</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.None)]
        public Dictionary<String, Object> NamedParameters;

        /// <summary>The e-mail recipients for completion notification</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("E-mail"), Description("The recipients for completion notification"), Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public MM_String_Collection MailRecipients
        {
            get { return _MailRecipients; }
            set { _MailRecipients = value; }
        }
        private MM_String_Collection _MailRecipients = new MM_String_Collection();

        /// <summary>Whether to include processed files into the email attachment</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("E-mail"), Description("Whether to include processed files into the email attachment"), DefaultValue(true)]
        public bool IncludeAttachments
        {
            get { return _IncludeAttachments; }
            set { _IncludeAttachments = value; }
        }
        private bool _IncludeAttachments = true;

        /// <summary>The subject of the outgoing e-mail</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("E-mail"),Description("The subject of the outgoing e-mail")]        
        public String Subject
        {
            get { return _Subject; }
            set { _Subject = value; }
        }
        private String _Subject;

        /// <summary>The SMTP server to be used</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("E-mail"), Description("The SMTP server to be used")]
        public String SMTPServer
        {
            get { return _SMTPServer; }
            set { _SMTPServer = value; }
        }
        private String _SMTPServer;

        /// <summary>The e-mail message to be sent</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("E-mail"), Description("The e-mail message to be sent")]
        public String Message
        {
            get { return _Message; }
            set { _Message = value; }
        }
        private String _Message;

        /// <summary>The file name for our output file</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Sharepoint"), Description("The path to write the output file on Sharepoint") ]
        public String SharepointFileName
        {
            get { return _SharepointFileName; }
            set { _SharepointFileName = value; }
        }
        private String _SharepointFileName;

        /// <summary>The folder on Sharepoint where the output should be placed</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Sharepoint"), Description("The folder on Sharepoint where the output should be placed"), Editor(typeof(MM_Folder_Browser), typeof(UITypeEditor))]
        public String SharepointFolder
        {
            get { return _SharepointFolder; }
            set { _SharepointFolder = value; }
        }
        private String _SharepointFolder;

        /// <summary>The file name for our output file</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Output"), Description("The path to write the output file")]
        public String TargetFileName
        {
            get { return _TargetFileName; }
            set { _TargetFileName = value; }
        }
        private String _TargetFileName;

        /// <summary>The folder on Target where the output should be placed</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Output"), Description("The folder where the output should be placed"), Editor(typeof(MM_Folder_Browser), typeof(UITypeEditor))]
        public String TargetFolder
        {
            get { return _TargetFolder; }
            set { _TargetFolder = value; }
        }
        private String _TargetFolder;

        /// <summary>When the process was started</summary>
        public DateTime StartTime;

        /// <summary>The state of our process</summary>
        public enum enumProcessState
        {
            /// <summary>The process has not yet run</summary>
            NotRun,
            /// <summary>The process is currently running</summary>
            Running,
            /// <summary>The process is completed.</summary>
            Completed,
            /// <summary>The process is completed, but had warnings</summary>
            CompletedWithWarnings,
            /// <summary>The process is completed, but had errors</summary>
            CompletedWithErrors,
            /// <summary>The process did not complete, had errors</summary>
            FailedWithErrors,
            /// <summary>The process is sending out completion email</summary>
            SendingEmail,
            /// <summary>The process is uploading the completed files to sharepoint</summary>
            UploadingToSharepoint
        }

        /// <summary>The current state of our process</summary>
        private enumProcessState _State = enumProcessState.NotRun;

        /// <summary>The current state of our process</summary>
        public enumProcessState State
        {
            get { return _State; }
            set
            {
                _State = value;
                if (ProcessLink != null)
                {
                    ProcessLink.CurrentValue = Current;
                    ProcessLink.TotalValue = Total;
                    ProcessLink.State = value;
                    ProcessLink.LastUpdate = DateTime.Now;
                    ProcessLink.UpdateRdb(Repository);
                }
            }
        }


        /// <summary>The link to our database process</summary>
        public MM_Database_Process ProcessLink;

        #endregion

        #region Initialization
        /// <summary>
        /// Assign all parameters that we can to our process
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public CIM_Processor(XmlElement xElem, Dictionary<String,Object>NamedParameters, MM_Repository Repository)
        {
            this.NamedParameters = NamedParameters;

            //Add ourselves to the processor, list of controls, and resize the group box
            ProgressCheck = new MM_Progress_Checkbox(this);
            ProgressCheck.CheckedChanged += new EventHandler(ProgressCheck_CheckedChanged);
            ProgressCheck.Tag = this;

            //Assign our XML values
            if (xElem != null)
            foreach (XmlAttribute xAttr in xElem.Attributes)
                MM_Serializable.AssignValue(this, xAttr.Name, xAttr.Value, xElem.OwnerDocument, Repository);
            ProgressCheck.Text = this.Description;
            ProgressCheck.HasOptions = HasProperties;

            this.Repository = Repository;
            
         
            State = enumProcessState.NotRun;
            
        }

        /// <summary>
        /// Update our selected property when the checked property changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProgressCheck_CheckedChanged(object sender, EventArgs e)
        {
            Selected = ProgressCheck.Checked;
        }
        #endregion

        #region Processing
        /// <summary>
        /// The process to be overriden
        /// </summary>
        /// <param name="DbConn">Our database connector</param>
        public abstract enumProcessState InitiateProcess(MM_Database_Connector DbConn);

        /// <summary>
        /// Execute our process in its own thread
        /// </summary>
        /// <param name="DbConn"></param>
        public void InitiateProcessInPooledThread(MM_Database_Connector DbConn)
        {
           
            ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessStub), DbConn);
        }

        /// <summary>
        /// Kick off our actual export process, converting state to Db connector
        /// </summary>
        /// <param name="State"></param>
        public void ProcessStub(Object State)
        {
            ProcessLink = new MM_Database_Process();
            ProcessLink.ProcessType = ProcessName;
            Object FoundObj;
            if (NamedParameters.TryGetValue("Model", out FoundObj) || NamedParameters.TryGetValue("CurrentModel", out FoundObj))
                ProcessLink.Model = (MM_Database_Model)FoundObj;
            ProcessLink.RunBy = Repository.CurrentUser;
            ProcessLink.SystemName = Environment.MachineName;
            ProcessLink.WriteRdb(Repository); 

            ProcessLink.State = this.State = enumProcessState.Running;
            ProcessLink.StartTime = this.StartTime = DateTime.Now;
            ProcessLink.LastUpdate = DateTime.Now;
            ProcessLink.UpdateRdb(Repository);

            this.ProcessLog.Remove(0, ProcessLog.Length);
            try
            {
                this.State = enumProcessState.Running;
                this.State = InitiateProcess(State as MM_Database_Connector);
            }
            catch (Exception ex)
            {
                ProcessLog.AppendLine("ERROR: " + ex.Message);
                ProcessLog.AppendLine(ex.StackTrace);
                ex = ex.InnerException;
                while (ex != null)
                {
                    ProcessLog.AppendLine("  " + ex.Message);
                    ProcessLog.AppendLine("  " + ex.StackTrace);
                    ex = ex.InnerException;
                }                
                this.State = enumProcessState.FailedWithErrors;
            }

            //Update our completion
            ProcessLink.EndTime = ProcessLink.LastUpdate = DateTime.Now;
            ProcessLink.UpdateRdb(Repository);

            //Now, send out emails.
            String ProcessedName = Path.Combine(ProcessString(TargetFolder), ProcessString(TargetFileName));
            List<object> OutObj = new List<object>();
            if (IncludeAttachments &&  File.Exists(ProcessedName))
                OutObj.Add(ProcessedName);

            enumProcessState curState = this.State;
            if (MailRecipients.Count > 0)
            {                
                this.State = enumProcessState.SendingEmail;
                StringBuilder sB = new StringBuilder();

                sB.AppendLine("This is the " + Application.ProductName + " version " + Application.ProductVersion + " run on " + Environment.MachineName + " by " + Environment.UserDomainName + "\\" + Environment.UserName);
                sB.AppendLine();
                sB.AppendLine("Start time: " + StartTime.ToString());
                sB.AppendLine("Completion time: " + DateTime.Now.ToString());
                sB.AppendLine("Duration: " + (DateTime.Now - StartTime).ToString());
                sB.AppendLine();
                sB.AppendLine(ProcessString(this.Message));
                sB.AppendLine();
                sB.AppendLine("Process parameters:");
                foreach (KeyValuePair<String, Object> kvp in NamedParameters)
                    if (kvp.Value == null)
                        sB.AppendLine(kvp.Key + ": (null)");
                    else
                        sB.AppendLine(kvp.Key + ": " + ProcessString(kvp.Value.ToString()));

                if (ProcessLog.Length > 0)
                {
                    sB.AppendLine();
                    sB.AppendLine("Log:");
                    sB.AppendLine(ProcessLog.ToString());
                }
                

                MM_Email_Sending.SendEmail(SMTPServer, Environment.UserName + "@ercot.com", Environment.UserName, ProcessString(Subject), MailRecipients, null, null, sB.ToString(), OutObj.ToArray());
                this.State = curState;
            }

            //Upload to sharepoint if possible
            if (!String.IsNullOrEmpty(SharepointFileName) && !String.IsNullOrEmpty(SharepointFolder))
            {
                String FileName = Path.Combine(ProcessString(TargetFolder), ProcessString(TargetFileName));
                if (File.Exists(FileName))
                {
                    this.State = enumProcessState.UploadingToSharepoint;
                    File.Copy(FileName, Path.Combine(ProcessString(SharepointFolder), ProcessString(SharepointFileName)));
                    this.State = curState;
                }
            }            

            //Upload our notes
            ProcessLink.Notes = ProcessLog.ToString();
            ProcessLink.LastUpdate = DateTime.Now;
            ProcessLink.UpdateRdb(Repository);
        }

        /// <summary>
        /// Update our process in the database, so that we can see the changes on our side
        /// </summary>
        public void UpdateProcess()
        {
            ProcessLink.CurrentValue = Current;
            ProcessLink.TotalValue = Total;
            ProcessLink.LastUpdate = DateTime.Now;
            ProcessLink.UpdateRdb(Repository);
        }
        #endregion

        #region String processing
        /// <summary>
        /// Process a string
        /// </summary>
        /// <param name="InString"></param>
        /// <returns></returns>
        public string ProcessString(String InString)
        {
            if (InString == null)
                InString = "";
            while (InString.Contains('['))
            {
                int StartBracket = InString.IndexOf('[');
                int EndBracket = InString.IndexOf(']', StartBracket);
                String CurValue = InString.Substring(StartBracket + 1, EndBracket - StartBracket - 1);
                
                if (CurValue.Contains("."))
                {
                    string[] splStr = CurValue.Split('.');
                    object inObj = NamedParameters[splStr[0]];
                    Type inType = inObj.GetType();
                    if (inType.GetProperty(splStr[1]) != null)
                        InString = InString.Substring(0, StartBracket) + inType.GetProperty(splStr[1]).GetValue(inObj, null).ToString() + InString.Substring(EndBracket + 1);
                    else if (inType.GetField(splStr[1]) != null)
                        InString = InString.Substring(0, StartBracket) + inType.GetField(splStr[1]).GetValue(inObj).ToString() + InString.Substring(EndBracket + 1);
                }
                else
                    InString = InString.Substring(0, StartBracket) + NamedParameters[CurValue].ToString() + InString.Substring(EndBracket + 1);
            }

            //Perform our x86 conversion as needed

            if (InString.Contains("\\Program Files\\") && !Directory.Exists(InString))
            {
                String TestString = InString.Replace("\\Program Files\\", "\\Program Files (x86)\\");
                if (Directory.Exists(TestString))
                    return TestString;
            }
            return InString;
        }
        #endregion

        /// <summary>
        /// Initialize and execute a specified process
        /// </summary>
        /// <param name="ProcessType"></param>
        /// <param name="CurModel"></param>
        /// <param name="Repository"></param>
        /// <param name="Parameters"></param>
        public static void ExecuteProcess(Type ProcessType, MM_Repository Repository, MM_Database_Model CurModel, params string[] Parameters)
        {
            XmlDocument xDoc = new XmlDocument();
            XmlElement xElem = xDoc.CreateElement(ProcessType.Name);
            xDoc.AppendChild(xElem);

            Dictionary<String, Object> NamedParameters = new Dictionary<string, object>();
            for (int a = 0; a < Parameters.Length; a += 2)
            {
                if (ProcessType.GetField(Parameters[a]) != null || ProcessType.GetProperty(Parameters[a]) != null)
                    xElem.Attributes.Append(xDoc.CreateAttribute(Parameters[a])).Value = Parameters[a + 1];
                NamedParameters.Add(Parameters[a], Parameters[a + 1]);
            }

            NamedParameters.Add("Model", CurModel);
            NamedParameters.Add("CurModel", CurModel);
            NamedParameters.Add("PriorModel", CurModel.PriorModel);
            CIM_Processor NewProc = (CIM_Processor)Activator.CreateInstance(ProcessType, xElem, NamedParameters, Repository);
            NewProc.SMTPServer = "[MailServer]" ;            
            NewProc.Subject = "Macomber Map Loader: " + NewProc.ProcessName + " completed for " + CurModel.Name + ".";
            NewProc.MailRecipients = new MM_String_Collection();
            NewProc.MailRecipients.Add(Environment.UserName + "@ercot.com");


            NewProc.IncludeAttachments = false;
            Repository.ActiveProcesses.Add(NewProc);

            if (ProcessQueue == null)
                (ProcessQueue = new Thread(new ParameterizedThreadStart(RunQueue))).Start(Repository);                                        
        }

        /// <summary>
        /// Run our queue, to handle processes one at a time
        /// </summary>
        /// <param name="state"></param>
        private static void RunQueue(object state)
        {
            MM_Repository Repository = (MM_Repository)state;
            while (true)
            {
                CIM_Processor ToRun = null;
                bool FoundRun = false;
                foreach (CIM_Processor Proc in Repository.ActiveProcesses)
                    if (Proc.State == enumProcessState.Running)
                        FoundRun = true;
                    else if (Proc.State == enumProcessState.NotRun && ToRun == null)
                        ToRun = Proc;
                
                if (!FoundRun && ToRun != null)
                    ToRun.InitiateProcessInPooledThread(Repository.Db);
                Thread.Sleep(1000);
            }
        }                   
    }
}
